package pzl.media.dsp;

import android.annotation.SuppressLint;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Environment;
import android.util.Log;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import static android.media.MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
import static android.media.MediaCodec.BUFFER_FLAG_KEY_FRAME;

/**
 * 1.一般的视频采集芯片输出的码流一般都是YUV数据流的形式，而从视频处理（例如H.264、MPEG视频编解码）的角度来说，也是在原始YUV码流进行编码和解析。
 * 2.要进行H264编码要先把数据转换成YUV图像格式的才可以。
 * 3.我们接收的到的图像信号一般是H.264格式的，移动设备接收到后，需要先解码成原始的YUV码流，然后又转换成RGB码流，将一帧一帧的RGB数据放到显存上才能显示出图像。而YUV到RGB的转换，很多ARM芯片上都有了。
 * <p>
 * AVC
 */
public class H264Encoder {
    private final static String TAG = "jinx_MediaCodec";

    private MediaCodec mediaCodec;
    /**
     * mediaCodec 压缩处理 输出流，取数据超时时间
     */
    private int TIMEOUT = 12000;
    /**
     * 传入的camera预览、surfaceview预览宽度
     */
    int width;
    /**
     * 传入的camera预览、surfaceview预览高度
     */
    int height;
    /**
     * 传入的帧率 如 30帧/s
     */
    int framerate;

    /**
     * 包含编解码器初始化/编解码器特定的数据，而不是媒体数据 PPS SPS
     */
    public byte[] configbyte;

    @SuppressLint("NewApi")
    public H264Encoder(int width, int height, int framerate, int bitrate) {

        this.width = width;
        this.height = height;
        this.framerate = framerate;
        //avc是h264
        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
        //色彩模式  YUV
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
        //“比特率/码率”  bit/sec   0或1被称作一个位，用小写b表示，即bit（位）。大写B表示byte即字节
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
        //帧率  frames/sec
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
        //I帧将要出现的时间间隔(整数)
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        //avc是h264
        try {
            mediaCodec = MediaCodec.createEncoderByType("video/avc");
        } catch (IOException e) {
            e.printStackTrace();
        }
        /**
         * MediaFormat format：输入数据的格式(解码器)或输出数据的所需格式(编码器)。传null等同于传递MediaFormat#MediaFormat作为空的MediaFormat。
         * Surface surface：指定Surface，用于解码器输出的渲染。如果编解码器不生成原始视频输出(例如，不是视频解码器)和/或想配置解码器输出ByteBuffer，则传null。
         * MediaCrypto crypto：指定一个crypto对象，用于对媒体数据进行安全解密。对于非安全的编解码器，传null。
         * int flags：当组件是编码器时，flags指定为常量CONFIGURE_FLAG_ENCODE。
         */
        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mediaCodec.start();

        initOutputStream();
    }


    /**
     * 视频编码保存的文件
     */
    private static String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/pzl.h264";
    private BufferedOutputStream bos;

    private void initOutputStream() {
        File file = new File(path);
        if (file.exists()) {
            file.delete();
        }
        try {
            bos = new BufferedOutputStream(new FileOutputStream(file));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * finish the decode/encode session , free up resources used by the codec instance
     */
    @SuppressLint("NewApi")
    private void stopEncoder() {
        try {
            mediaCodec.stop();
            mediaCodec.release();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public boolean isRuning = false;

    /**
     * 停止采集编码任务
     */
    public void stopThread() {
        isRuning = false;
        try {
            stopEncoder();
            bos.flush();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 启动mediaCodec编码任务线程
     */
    public void startEncoderThread() {
        Thread EncoderThread = new Thread(new Runnable() {

            @SuppressLint("NewApi")
            @Override
            public void run() {
                isRuning = true;
                //传给mediaCodec处理的camera原始数据（一帧 YUV图像）
                byte[] input = null;
                long pts = 0;
                long generateIndex = 0;

                while (isRuning) {
                    if (H264EncodeCameraActivity.YUVQueue.size() > 0) {
                        input = H264EncodeCameraActivity.YUVQueue.poll();
                        //图像旋转90度，摆正
                        Log.i(TAG, "取缓存 预览帧 byte[] length = " + input.length);
                        byte[] yuv420sp = new byte[width * height * 3 / 2];
                        //Log.i(TAG, "取缓存 yuv420sp byte[] length = " + yuv420sp.length);
                        NV21ToNV12(input, yuv420sp, width, height);
                        input = yuv420sp;
                    }
                    if (input != null) {
                        try {
                            long startMs = System.currentTimeMillis();
                            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();//待处理的数据输入流 (可能存在其它多个程序同时在使用 mediaCodec编解码能力)
                            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();//处理完成后数据的输出流 (可能存在其它多个程序同时在使用 mediaCodec编解码能力)
                            int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);//timeoutUs < 0，无限期等待可用的输入buffer （其它程序处理完则会有空闲）
                            if (inputBufferIndex >= 0) {//索引>0 则表示有空闲的ByteBuffer资源了，索引index为ByteBuffer空闲的位置
                                pts = computePresentationTime(generateIndex);
                                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                                inputBuffer.clear();
                                inputBuffer.put(input);//填充提交待编码的原始数据
                                mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0);
                                generateIndex += 1;
                            }

                            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();//取出编码数据对应的描述信息
                            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT);//获取编码处理结果（第一次）
                            while (outputBufferIndex >= 0) {// 索引>0  编码处理结束
                                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                                byte[] outData = new byte[bufferInfo.size];
                                outputBuffer.get(outData);//拷贝取出处理后的数据
                                if (bufferInfo.flags == BUFFER_FLAG_CODEC_CONFIG) {//buffer包含编解码器初始化/编解码器特定的数据，而不是媒体数据 PPS SPS
                                    configbyte = new byte[bufferInfo.size];
                                    configbyte = outData;
                                } else if (bufferInfo.flags == BUFFER_FLAG_KEY_FRAME) {//buffer包含关键帧的数据
                                    //拼装I帧数据  描述信息+I帧数据
                                    byte[] keyframe = new byte[bufferInfo.size + configbyte.length];
                                    System.arraycopy(configbyte, 0, keyframe, 0, configbyte.length);
                                    System.arraycopy(outData, 0, keyframe, configbyte.length, outData.length);
                                    bos.write(keyframe, 0, keyframe.length);
                                } else {//其它普通帧的数据
                                    bos.write(outData, 0, outData.length);
                                }
                                //释放对mediaCodec该段outputBuffer资源的占用
                                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT);//获取编码处理结果（循环）
                            }

                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    } else {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        EncoderThread.start();

    }

    /**
     * 用mediacode编码h264时，因为mediacode编码视频只支持yuv420sp的nv12，需要将nv21转为nv12
     * <p>
     * yuv420sp的分两种，nv21和nv12。
     * <p>
     * https://blog.csdn.net/baidu_31872269/article/details/70315193
     * <p>
     * https://www.cnblogs.com/raomengyang/p/5793096.html
     * https://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html
     * 手机从摄像头采集的预览数据一般都是NV21。NV12、NV21（属于YUV420）
     * <p>
     * NV21(4*4像素图像例子):
     * Y Y Y Y
     * Y Y Y Y
     * Y Y Y Y
     * Y Y Y Y
     * V U V U
     * V U V U
     * <p>
     * NV12(4*4像素图像例子):
     * Y Y Y Y
     * Y Y Y Y
     * Y Y Y Y
     * Y Y Y Y
     * U V U V
     * U V U V
     */
    private void NV21ToNV12(byte[] nv21, byte[] nv12, int width, int height) {
        if (nv21 == null || nv12 == null) {
            return;
        }
        int framesize = width * height;
        int i = 0, j = 0;
        System.arraycopy(nv21, 0, nv12, 0, framesize);
        for (i = 0; i < framesize; i++) {
            nv12[i] = nv21[i];
        }
        for (j = 0; j < framesize / 2; j += 2) {
            nv12[framesize + j - 1] = nv21[j + framesize];
        }
        for (j = 0; j < framesize / 2; j += 2) {
            nv12[framesize + j] = nv21[j + framesize - 1];
        }
    }

    /**
     * Generates the presentation time for frame N, in microseconds.
     * 根据帧的索引计算pts
     */
    private long computePresentationTime(long frameIndex) {
        return 132 + frameIndex * 1000000 / framerate;
    }


}
